package com.zhendong.gulimall.cart.interceptor;

import com.google.common.collect.Maps;
import com.zhendong.common.constant.AuthServiceConstant;
import com.zhendong.common.constant.CartServiceConstant;
import com.zhendong.common.vo.MemberResVo;
import com.zhendong.gulimall.cart.vo.UserInfoTo;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Map;
import java.util.UUID;

/**
 * @author 孟享广
 * @date 2021-02-03 3:01 下午
 * @description 在执行目标方法之前，判断用户的登录状态，并封装传递给controller的目标请求
 */
//@Component TODO 这里直接写个拦截器是不起作用的，要把它注册到GulimallWebConfig里面
public class CartInterceptor implements HandlerInterceptor {

    //ThreadLocal 同一线程上信息共享【拦截器->controller->service->dao  springboot默认会分配一个线程去执行，所以这里用ThreadLocal特别合适】
    public static ThreadLocal<UserInfoTo> threadLocal = new ThreadLocal<>();
    //TODO zhen假设用map来替代ThreadLocal就是这样，但做不到线程唯一，变成了内存共享，并且ThreadLocal不存在并发安全问题。我们定义ThreadLocal实际上就是在定义map的key，ThreadLocal里的内容就相当于value
    //TODO zhen有个问题，如果下次请求进来当前线程，那没有删除这个map不就带给下个请求了 链接 https://blog.csdn.net/luzhensmart/article/details/86765689
    public static Map<String, UserInfoTo> threadLocalMap = Maps.newHashMap();

    /**
     * 在目标方法执行之前
     * TODO HttpServletRequest是springSession包装后的东西
     */
    @Override

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        UserInfoTo userInfoTo = new UserInfoTo();
        HttpSession session = request.getSession();
        MemberResVo memberResVo = (MemberResVo) session.getAttribute(AuthServiceConstant.LOGIN_USER);   //配置过了springsession，这里的request取session其实已经是包装过的request，包装过后是去redis中取值的
        if (memberResVo != null) {
            //说明用户登录了
            userInfoTo.setUserId(memberResVo.getId());
        }

        //只要有user-key 就赶紧取出value
        Cookie[] cookies = request.getCookies();
        if (cookies != null && cookies.length > 0) {
            //有cookie 可能是临时用户，但是此方法针对登录用户
            for (Cookie cookie : cookies) {
                String name = cookie.getName();
                if (name.equals(CartServiceConstant.TEMP_USER_COOKIE_NAME)) {
                    userInfoTo.setUserKey(cookie.getValue());
                    //执行到这，说明是临时用户
                    userInfoTo.setTempUser(true);
                }
            }
        }

        //如果没有登录 就准备临时set一个cookie，首先设置To的userKey
        if (StringUtils.isEmpty(userInfoTo.getUserKey())) {
            String uuid = UUID.randomUUID().toString();
            userInfoTo.setUserKey(uuid);
        }

        //目标方法执行前，放入 threadLocal
        //TODO 我一直纠结的问题是下一次请求进来又不是这个线程执行的怎么办，无所谓，这次请求结束一定会删掉threadLocal里的userInfo，下次新的请求进来会把新的userInfo放进去，新旧请求线程互不牵扯
        //TodO 其次，threadLocal觉得可以换成一个全局Map，每次请求都把userInfo放到map中，那map中的key怎么给呢，这是错误的，而threadLocal每次进来重新赋值，请求完了直接删除非常好
        threadLocal.set(userInfoTo);
        threadLocalMap.put("threadMap",userInfoTo);

        //只要来到目标方法都放行 无条件放行
        return true;
    }

    /**
     * 业务执行之后，让浏览器保存cookie
     * 分配临时用户，让浏览器保存
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

        UserInfoTo userInfoTo = threadLocal.get();
        UserInfoTo userInfoTo1 = threadLocalMap.get("threadMap");
        //如果没有临时用户，一定要保存一个临时用户
        if (!userInfoTo.isTempUser()) {
            //不是临时用户
            Cookie cookie = new Cookie(CartServiceConstant.TEMP_USER_COOKIE_NAME, userInfoTo.getUserKey());
            //设置cookie作用域
            cookie.setDomain("gulimall.com");
            //cookie的过期时间
            cookie.setMaxAge(CartServiceConstant.TEMP_USER_COOKIE_TIMEOUT);
            response.addCookie(cookie);
        }
        //TODO zhen我觉得应该在每次请求结束之后释放掉threadLocal，避免内存泄露
        threadLocal.remove();
        threadLocalMap.clear();
    }
}
